﻿using Castle.DynamicProxy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TianYuan.Utils.Extensions;

namespace TianYuan.Core.Caching.Interception
{
    public class CacheInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var method = invocation.MethodInvocationTarget;
            
            if (method.IsDefined(typeof(CachingAttribute), false))
            {
                var cachingAttribute = (CachingAttribute)method.GetCustomAttributes(typeof(CachingAttribute), false)[0];
                var Key = GetKey(cachingAttribute, invocation);
                if (string.IsNullOrEmpty(Key))
                {
                    invocation.Proceed();
                    return;
                }
                
                switch (cachingAttribute.Method)
                {
                    case CachingMethod.Put:
                    case CachingMethod.Get:

                        ICache cache = GetCacher(cachingAttribute, invocation);
                        object obj = null;
                        if (invocation.Method.ReturnType.IsValueType)
                        {
                            obj = cache.ObjectGet(Key);
                        }
                        else
                        {
                            MethodInfo mi = cache.GetType().GetMethod("Get").MakeGenericMethod(new Type[] { invocation.Method.ReturnType });
                            obj = mi.Invoke(cache, parameters: new[] { Key });
                        }
                        
                        //var obj = cache.Get(Key);
                        if (obj!=null)
                        {
                            invocation.ReturnValue = obj;
                            return;
                        }
                        else
                        {
                            invocation.Proceed();
                            if (invocation.ReturnValue != null)
                            {
                                cache.Set(Key, invocation.ReturnValue, DateTime.Now.AddSeconds(cachingAttribute.CacheSeconds));
                            }
                            return;
                        }
                    case CachingMethod.Remove:
                        ICache cacheR = GetCacher(cachingAttribute, invocation);
                        cacheR.Remove(Key);
                        invocation.Proceed();
                        return;
                    case CachingMethod.Clear:
                        var removeKeysregion = cachingAttribute.CorrespondingTypes;
                        foreach (var removeKey in removeKeysregion)
                        {
                            ICache cache_temp = CacheManager.GetCacher(removeKey);
                            cache_temp.Clear();
                        }
                        invocation.Proceed();
                        return;
                    default: break;
                }
            }

            invocation.Proceed();
        }
        #region Private Methods
        /// <summary>
        /// 获取缓存者实例
        /// </summary>
        /// <param name="cachingAttribute"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        private ICache GetCacher(CachingAttribute cachingAttribute, IInvocation input)
        {
            if (cachingAttribute.CacheRegion.NotNullOrWhiteSpace())
            {
                return CacheManager.GetCacher(cachingAttribute.CacheRegion);
            }
            else
            {
                return CacheManager.GetCacher(input.Method.ReturnType); 
            }
        }


        /// <summary>
        /// 根据指定的<see cref="CachingAttribute"/>以及<see cref="IInvocation"/>实例，
        /// 获取与某一特定参数值相关的键名。
        /// </summary>
        /// <param name="cachingAttribute"><see cref="CachingAttribute"/>实例。</param>
        /// <param name="input"><see cref="IInvocation"/>实例。</param>
        /// <returns>与某一特定参数值相关的键名。</returns>
        private string GetKey(CachingAttribute cachingAttribute, IInvocation input)
        {
            switch (cachingAttribute.Method)
            {
                // 如果是Remove，则不存在特定值键名，所有的以该方法名称相关的缓存都需要清除
                case CachingMethod.Remove:
                    return null;
                // 如果是Get或者Put，则需要产生一个针对特定参数值的键名
                case CachingMethod.Get:
                case CachingMethod.Put:
                    if (cachingAttribute.Key.IsNullOrWhiteSpace()) {
                        return GetMethodParamString(input);
                    }
                    else
                    {
                        return GetEL(cachingAttribute,input);
                    }
                case CachingMethod.Clear:
                    return "key";
                default:
                    throw new InvalidOperationException("无效的缓存方式。");
            }
        }
        /// <summary>
        /// 获取表达式参数设置的缓存键值
        /// </summary>
        /// <param name="cachingAttribute"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        private static string GetEL(CachingAttribute cachingAttribute, IInvocation input)
        {

            List<string> els = cachingAttribute.Key.Replace(" ","").Split('+').ToList();

            Type tp2 = typeof(ICacheParam);
            StringBuilder sb = new StringBuilder();
            MethodInfo method = input.Method;
            //用方法名称构造
            if (els.Where(m => m=="methodname").Any())
            {
                sb.Append(method.DeclaringType.FullName + "." + method.Name + "(");
            }
            //用所有参数构造，忽略单个参数
            if (els.Where(m=>m=="#args").Any()&& !els.Where(m => m.Contains("#args[")).Any())
            {
                if (input.Arguments != null &&
                input.Arguments.Count() > 0)
                {
                    
                    for (int i = 0; i < input.Arguments.Count(); i++)
                    {
                        AppendParams(input, sb, tp2, i);
                    }
                }
            }
            //按指定参数拼接
            if (!els.Where(m => m=="#args").Any() && els.Where(m => m.Contains("#args[")).Any())
            {
                //匹配数字
                string patternNum = @"^[1-9]\d*$";
                var elstemp = els.Where(m => m.Contains("#args[")).ToList();
                foreach (var item in elstemp)
                {
                    var argstemp = item.Replace("#args[", "").Replace("]", "").Replace(" ", "");
                    int index = argstemp.CastTo<int>();
                    AppendParams(input, sb, tp2, index);
                }
            }

            var parameters = input.Method.GetParameters();
            
            //按参数属性拼接
            foreach (var item in els)
            {
                if (item.IndexOf('.') > 0)
                {
                    string[] ptemps = item.Replace("#","").Split('.').ToArray();
                    ParameterInfo parameter = parameters.Where(m => m.Name == ptemps[0]).FirstOrDefault();
                    if (parameter!=null)
                    {
                        var obj = input.GetArgumentValue(parameter.Position);
                        var field = obj.GetType().GetProperty(ptemps[1]);
                        if (field != null)
                        {
                            sb.Append(field.GetValue(obj).ToString());
                        }
                    }
                }
            }

            if (els.Where(m => m=="methodname").Any())
            {
                sb.Append(")");
            }
            return sb.ToString();
        }
        /// <summary>
        /// 绑定参数
        /// </summary>
        /// <param name="input"></param>
        /// <param name="sb"></param>
        /// <param name="tp2"></param>
        /// <param name="i"></param>
        private static void AppendParams(IInvocation input, StringBuilder sb, Type tp2, int i)
        {
            Type tp1 = input.Arguments[i].GetType();

            if (tp2.IsAssignableFrom(tp1))
            {
                ICacheParam param = input.Arguments[i] as ICacheParam;
                sb.Append(param.GetCacheKey());//
            }
            else
            {
                sb.Append(input.Arguments[i].ToString());//或者如果参数为对象，需要重写查询参数的ToString方法
            }
            if (i != input.Arguments.Count() - 1)
                sb.Append("_");
        }

        /// <summary>
        /// 获取方法参数构造的string用来做缓存键值，按照方法的所有参数进行组合
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private static string GetMethodParamString(IInvocation input)
        {
            StringBuilder sb = new StringBuilder();
            MethodInfo method = input.Method;
            sb.Append(method.DeclaringType.FullName + "." + method.Name + "(");
            if (input.Arguments != null &&
                input.Arguments.Count() > 0)
            {
                Type tp2 = typeof(ICacheParam);
                for (int i = 0; i < input.Arguments.Count(); i++)
                {
                    Type tp1 = input.Arguments[i].GetType();
                    
                    if (tp2.IsAssignableFrom(tp1))
                    {
                        ICacheParam param = input.Arguments[i] as ICacheParam;
                        sb.Append(param.GetCacheKey());//
                    }
                    else
                    {
                        sb.Append(input.Arguments[i].ToString());//或者如果参数为对象，需要重写查询参数的ToString方法
                    }
                    if (i != input.Arguments.Count() - 1)
                        sb.Append("_");
                }
            }

            sb.Append(")");
            return sb.ToString();
        }
         
        #endregion
    }
}
